//----------------------------------------------------- // Kitchen Timer with TM1637 Display // by: mircemk // License: GNU GPl 3.0 // Created: April 2025 //----------------------------------------------------- #include // Pin definitions #define CLK 2 #define DIO 3 #define POT_PIN A0 #define START_BUTTON 4 #define RESET_BUTTON 5 #define BUZZER_PIN 6 #define RANGE_BUTTON 7 // Constants #define QUANTIZE_INTERVAL 30 #define POT_SMOOTHING 30 #define POT_READ_DELAY 50 #define ALARM_FREQ 500 #define ALARM_ON_TIME 200 #define ALARM_OFF_TIME 200 #define COLON_ON_TIME 500 #define COLON_OFF_TIME 500 #define RANGE_DISPLAY_TIME 1000 #define BUTTON_DEBOUNCE_TIME 300 // Timer ranges in seconds const int TIMER_RANGES[] = {600, 1800, 3600}; // 10min, 30min, 60min const int NUM_RANGES = 3; // Display instance TM1637Display display(CLK, DIO); // Variables unsigned long previousMillis = 0; const long interval = 1000; bool timerRunning = false; int remainingTime = 0; bool colonState = true; unsigned long lastBuzzTime = 0; unsigned long lastColonToggle = 0; bool alarmOn = false; bool displayOn = true; int lastDisplayedTime = -1; unsigned long lastPotRead = 0; unsigned long lastDisplayFlash = 0; int currentRangeIndex = 2; // Start with 60min range (index 2) unsigned long rangeDisplayStartTime = 0; bool showingRange = false; unsigned long lastRangeButtonPress = 0; // State enumeration enum TimerState { IDLE, SHOWING_RANGE, RUNNING, ALARMING }; TimerState currentState = IDLE; void setup() { pinMode(START_BUTTON, INPUT_PULLUP); pinMode(RESET_BUTTON, INPUT_PULLUP); pinMode(RANGE_BUTTON, INPUT_PULLUP); pinMode(BUZZER_PIN, OUTPUT); display.setBrightness(0x0a); updateDisplay(quantizeTime(readSmoothedPot())); } void loop() { unsigned long currentMillis = millis(); switch(currentState) { case SHOWING_RANGE: // Stay in range display mode until time elapsed if (currentMillis - rangeDisplayStartTime >= RANGE_DISPLAY_TIME) { currentState = IDLE; lastDisplayedTime = -1; // Force pot reading update } else { // Keep showing range displayRange(TIMER_RANGES[currentRangeIndex]); return; // Skip all other processing while showing range } break; case IDLE: // Handle range button if (digitalRead(RANGE_BUTTON) == LOW) { if (currentMillis - lastRangeButtonPress >= BUTTON_DEBOUNCE_TIME) { currentRangeIndex = (currentRangeIndex + 1) % NUM_RANGES; rangeDisplayStartTime = currentMillis; lastRangeButtonPress = currentMillis; currentState = SHOWING_RANGE; displayRange(TIMER_RANGES[currentRangeIndex]); return; // Exit loop immediately after changing to range display } } // Handle potentiometer input if (currentMillis - lastPotRead > POT_READ_DELAY) { lastPotRead = currentMillis; int potTime = quantizeTime(readSmoothedPot()); if (potTime != lastDisplayedTime) { colonState = true; updateDisplay(potTime); lastDisplayedTime = potTime; } } // Handle start button if (digitalRead(START_BUTTON) == LOW) { delay(50); if (digitalRead(START_BUTTON) == LOW) { remainingTime = quantizeTime(readSmoothedPot()); currentState = RUNNING; previousMillis = currentMillis; lastDisplayedTime = -1; colonState = true; lastColonToggle = currentMillis; } } break; case RUNNING: // Handle colon blinking if (colonState && (currentMillis - lastColonToggle >= COLON_ON_TIME)) { colonState = false; lastColonToggle = currentMillis; updateDisplay(remainingTime); } else if (!colonState && (currentMillis - lastColonToggle >= COLON_OFF_TIME)) { colonState = true; lastColonToggle = currentMillis; updateDisplay(remainingTime); } // Update timer if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; if (remainingTime > 0) { remainingTime--; updateDisplay(remainingTime); } if (remainingTime == 0) { currentState = ALARMING; } } // Check reset button if (digitalRead(RESET_BUTTON) == LOW) { delay(50); if (digitalRead(RESET_BUTTON) == LOW) { resetTimer(); } } break; case ALARMING: handleAlarm(); if (digitalRead(RESET_BUTTON) == LOW) { delay(50); if (digitalRead(RESET_BUTTON) == LOW) { resetTimer(); } } break; } } void displayRange(int rangeInSeconds) { int minutes = rangeInSeconds / 60; uint8_t segments[4]; segments[0] = display.encodeDigit(minutes / 10); segments[1] = display.encodeDigit(minutes % 10) | 0x80; // Force colon on segments[2] = display.encodeDigit(0); segments[3] = display.encodeDigit(0); display.setSegments(segments); } int readSmoothedPot() { long total = 0; for (int i = 0; i < POT_SMOOTHING; i++) { total += analogRead(POT_PIN); delay(1); } int average = total / POT_SMOOTHING; return map(average, 0, 1023, 0, TIMER_RANGES[currentRangeIndex]); } int quantizeTime(int seconds) { int quantized = (seconds / QUANTIZE_INTERVAL) * QUANTIZE_INTERVAL; return constrain(quantized, 0, TIMER_RANGES[currentRangeIndex]); } void updateDisplay(int timeInSeconds) { if (currentState == ALARMING && !displayOn) { display.clear(); return; } int minutes = timeInSeconds / 60; int seconds = timeInSeconds % 60; uint8_t segments[4]; segments[0] = display.encodeDigit(minutes / 10); segments[1] = display.encodeDigit(minutes % 10); segments[2] = display.encodeDigit(seconds / 10); segments[3] = display.encodeDigit(seconds % 10); if (colonState) { segments[1] |= 0x80; } display.setSegments(segments); lastDisplayedTime = timeInSeconds; } void handleAlarm() { unsigned long currentMillis = millis(); // Handle display flashing if (currentMillis - lastDisplayFlash >= 500) { lastDisplayFlash = currentMillis; displayOn = !displayOn; updateDisplay(0); } // Handle intermittent alarm sound if (currentMillis - lastBuzzTime >= (alarmOn ? ALARM_ON_TIME : ALARM_OFF_TIME)) { lastBuzzTime = currentMillis; alarmOn = !alarmOn; if (alarmOn) { tone(BUZZER_PIN, ALARM_FREQ); } else { noTone(BUZZER_PIN); } } } void resetTimer() { currentState = IDLE; timerRunning = false; noTone(BUZZER_PIN); alarmOn = false; displayOn = true; colonState = true; lastDisplayedTime = -1; updateDisplay(quantizeTime(readSmoothedPot())); }